package com.openApi.filters;

import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.openApi.HeaderConstant;
import com.openApi.utils.AesUtils;
import com.openApi.utils.Result;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.filter.factory.rewrite.CachedBodyOutputMessage;
import org.springframework.cloud.gateway.support.BodyInserterContext;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.BodyInserter;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * 签名安全性验证
 *
 * @author Administrator
 */
@Component
public class SafetyFilter implements GlobalFilter, Ordered {

    private static final String CONTENT_TYPE_JSON = "application/json";

    @Value("#{'${spring.cloud.gateway.mqUrls}'.split(',')}")
    private List<String> mqUrls;

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @Autowired
    private ServerCodecConfigurer codecConfigurer;

    /**
     * 数据格式：
     * appId
     * timeStamp
     * signature
     * data:消费记录,List集合
     *
     * @param exchange
     * @param chain
     * @return
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String url = exchange.getRequest().getURI().getPath();
        ServerHttpRequest request = exchange.getRequest();
        String method = request.getMethodValue();
        //排除特殊接口 不校验
        if (!mqUrls.contains(url)) {
            return chain.filter(exchange);
        }
        // mediaType
        MediaType mediaType = exchange.getRequest().getHeaders().getContentType();

        Result result = new Result();
        if (HttpMethod.POST.matches(method)) {
            ServerRequest serverRequest = ServerRequest.create(exchange, codecConfigurer.getReaders());
            Mono<String> stringMono = serverRequest.bodyToMono(String.class);
            Mono<String> modifiedBody = stringMono
                    .flatMap(
                            body -> {
                                if (null != mediaType && mediaType.toString().contains(CONTENT_TYPE_JSON)) {
                                    Map map = JSONObject.parseObject(body, Map.class);
                                    check(map, exchange.getRequest().getHeaders(), result);
                                } else {
                                    result.put("code", "5001");
                                    result.put("message", "请求错误");
                                }
                                return Mono.just(body);
                            }
                    );
            ServerHttpResponse response = exchange.getResponse();
            BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody, String.class);
            HttpHeaders headers = new HttpHeaders();
            headers.putAll(exchange.getRequest().getHeaders());
            headers.remove(HttpHeaders.CONTENT_LENGTH);

            CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange, headers);
            Mono mono = bodyInserter.insert(outputMessage, new BodyInserterContext());
            return mono.then(Mono.defer(() -> {
                if (!"0".equals(result.get("code"))) {
                    return responseError(response, result);
                }
                ServerHttpRequestDecorator decorator = new ServerHttpRequestDecorator(
                        exchange.getRequest()) {
                    @Override
                    public HttpHeaders getHeaders() {
                        long contentLength = headers.getContentLength();
                        HttpHeaders httpHeaders = new HttpHeaders();
                        httpHeaders.putAll(super.getHeaders());
                        if (contentLength > 0) {
                            httpHeaders.setContentLength(contentLength);
                        } else {
                            httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked");
                        }
                        return httpHeaders;
                    }

                    @Override
                    public Flux<DataBuffer> getBody() {
                        return outputMessage.getBody();
                    }
                };

                return chain.filter(exchange.mutate().request(decorator).build());
            }));
        } else if (HttpMethod.GET.matches(method)) {
            check(request.getQueryParams(), exchange.getRequest().getHeaders(), result);

            ServerHttpResponse response = exchange.getResponse();
            if (!"0".equals(result.get("code"))) {
                return responseError(response, result);
            }
            return chain.filter(exchange);
        }
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return 0;
    }

    /**
     * 安全验证
     *
     * @param map
     * @return
     */
    public boolean check(Map map, HttpHeaders headers, Result result) {
        if (!headers.containsKey(HeaderConstant.APPKEY)) {
            result.put("code", "4001");
            result.put("message", "appKey is not exist");
            return false;
        } else if (!headers.containsKey(HeaderConstant.SIGNATURE)) {
            result.put("code", "4001");
            result.put("message", "signature is not exist");
            return false;
        } else if (!headers.containsKey(HeaderConstant.TIMESTAMP)) {
            result.put("code", "4001");
            result.put("message", "timestamp is not exist");
            return false;
        } else if (!headers.containsKey(HeaderConstant.NONCE)) {
            result.put("code", "4001");
            result.put("message", "nonce is not exist");
            return false;
        }


        String appKey = headers.getFirst(HeaderConstant.APPKEY);
        String signature = headers.getFirst(HeaderConstant.SIGNATURE);
        String nonce = headers.getFirst(HeaderConstant.NONCE);

        long timestamp = Long.parseLong(headers.getFirst(HeaderConstant.TIMESTAMP));
        long currentTimeMillis = System.currentTimeMillis();
        //有效时长为5分钟
        if (currentTimeMillis - timestamp > 1000 * 60 * 5) {
            result.put("code", "4002");
            result.put("message", "request timestamp is expired");
            return false;
        }

        String keySecret = redisTemplate.opsForValue().get(appKey);

        if (StringUtils.isBlank(keySecret)) {
            result.put("code", "4003");
            result.put("message", "appKey is wrong");
            return false;
        }

        try {
            if (!AesUtils.check(map, signature, keySecret)) {
                result.put("code", "4004");
                result.put("message", "signature is wrong");
                return false;
            }
        } catch (Exception e) {
            result.put("code", "4004");
            result.put("message", "signature is wrong");
            return false;
        }
        String requestId = timestamp + "" + nonce;
        if (StringUtils.isBlank(redisTemplate.opsForValue().get(appKey + "_" + signature))) {
            //放入redis 防止接口重放
            redisTemplate.opsForValue().set(requestId, requestId, 5, TimeUnit.MINUTES);
            result.put("code", "0");
        } else {
            result.put("code", "4005");
            result.put("message", "reject duplicate request ");
            return false;
        }

        return true;
    }

    /**
     * 不安全的返回
     *
     * @param response
     * @param error
     * @return
     */

    private Mono<Void> responseError(ServerHttpResponse response, Result error) {
        // 将信息转换为 JSON
        ObjectMapper objectMapper = new ObjectMapper();
        byte[] data = new byte[0];
        try {
            data = objectMapper.writeValueAsBytes(error);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
        // 输出错误信息到页面
        DataBuffer buffer = response.bufferFactory().wrap(data);
        response.setStatusCode(HttpStatus.UNAUTHORIZED);
        response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
        return response.writeWith(Mono.just(buffer));
    }


}
